package com.ms_square.debugoverlay;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.net.Uri;
import android.os.Build;
import android.os.IBinder;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.content.PermissionChecker;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.Toast;
import java.util.Collections;
import java.util.List;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
class OverlayViewManager {
private static final String TAG = OverlayViewManager.class.getSimpleName();
private final Context context;
private final DebugOverlay.Config config;
private final WindowManager windowManager;
private List<OverlayModule> overlayModules = Collections.emptyList();
private ViewGroup rootView;
private boolean overlayPermissionRequested;
public OverlayViewManager(@NonNull Context context, DebugOverlay.Config config) {
this.context = context;
this.config = config;
this.windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}
public void setOverlayModules(@NonNull List<OverlayModule> overlayModules) {
this.overlayModules = overlayModules;
}
public void showDebugSystemOverlay() {
if (config.isAllowSystemLayer() && rootView == null) {
if (!canDrawOnSystemLayer(context, getWindowTypeForOverlay(true))) {
Toast.makeText(context, R.string.debugoverlay_overlay_permission_prompt, Toast.LENGTH_LONG).show();
requestDrawOnSystemLayerPermission(context);
overlayPermissionRequested = true;
return;
}
overlayPermissionRequested = false;
rootView = createRoot();
int layoutParamsWidth = WindowManager.LayoutParams.WRAP_CONTENT;
for (OverlayModule overlayModule : overlayModules) {
View view = overlayModule.createView(rootView, config.getTextColor(), config.getTextSize(), config.getTextAlpha());
if (view.getParent() == null) {
if (view.getLayoutParams() != null && view.getLayoutParams().width == MATCH_PARENT) {
layoutParamsWidth = WindowManager.LayoutParams.MATCH_PARENT;
}
rootView.addView(view);
}
}
WindowManager.LayoutParams params = createLayoutParams(config.isAllowSystemLayer(), layoutParamsWidth, null);
windowManager.addView(rootView, params);
}
}
public void hideDebugSystemOverlay() {
if (config.isAllowSystemLayer() && rootView != null) {
windowManager.removeView(rootView);
rootView = null;
}
}
public boolean isSystemOverlayShown() {
return rootView != null;
}
public boolean isOverlayPermissionRequested() {
return overlayPermissionRequested;
}
public OverlayViewAttachStateChangeListener createAttachStateChangeListener() {
return new OverlayViewAttachStateChangeListener();
}
private WindowManager.LayoutParams createLayoutParams(boolean allowSystemLayer, int width, IBinder windowToken) {
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.width = width;
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
if (windowToken != null) {
layoutParams.token = windowToken;
}
//noinspection WrongConstant
layoutParams.type = getWindowTypeForOverlay(allowSystemLayer);
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
layoutParams.format = PixelFormat.TRANSLUCENT;
layoutParams.gravity = config.getPosition().getGravity();
return layoutParams;
}
private ViewGroup createRoot() {
LinearLayout overlayRoot = new LinearLayout(context);
overlayRoot.setOrientation(LinearLayout.VERTICAL);
if (config.getBgColor() != Color.TRANSPARENT) {
overlayRoot.setBackgroundColor(config.getBgColor());
}
return overlayRoot;
}
class OverlayViewAttachStateChangeListener implements View.OnAttachStateChangeListener {
private ViewGroup _rootView;
public void onActivityResumed() {
if (_rootView != null && _rootView.getChildCount() > 0) {
if (DebugOverlay.DEBUG) {
Log.i(TAG, "overlay views recreated on Activity's onResume");
}
_rootView.removeAllViews();
for (OverlayModule overlayModule : overlayModules) {
View view = overlayModule.createView(_rootView, config.getTextColor(), config.getTextSize(), config.getTextAlpha());
if (view.getParent() == null) {
_rootView.addView(view);
}
}
// force-update recreated views with the latest data
for (OverlayModule overlayModule : overlayModules) {
overlayModule.notifyObservers();
}
}
}
@Override
public void onViewAttachedToWindow(View v) {
if (DebugOverlay.DEBUG) {
Log.i(TAG, "onViewAttachedToWindow");
}
_rootView = createRoot();
int layoutParamsWidth = WindowManager.LayoutParams.WRAP_CONTENT;
for (OverlayModule overlayModule : overlayModules) {
View view = overlayModule.createView(_rootView, config.getTextColor(), config.getTextSize(), config.getTextAlpha());
if (view.getParent() == null) {
if (view.getLayoutParams() != null && view.getLayoutParams().width == MATCH_PARENT) {
layoutParamsWidth = WindowManager.LayoutParams.MATCH_PARENT;
}
_rootView.addView(view);
}
}
windowManager.addView(_rootView, createLayoutParams(config.isAllowSystemLayer(),
layoutParamsWidth, v.getWindowToken()));
}
@Override
public void onViewDetachedFromWindow(View v) {
if (DebugOverlay.DEBUG) {
Log.i(TAG, "onViewDetachedFromWindow");
}
windowManager.removeViewImmediate(_rootView);
v.removeOnAttachStateChangeListener(this);
}
}
public static void requestDrawOnSystemLayerPermission(@NonNull Context context) {
if (!hasSystemAlertPermissionInManifest(context)) {
throw new UnsupportedOperationException("'SYSTEM_ALERT_WINDOW' must be explicitly added in the manifest.");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// request permission
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + context.getPackageName()));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(intent);
}
}
public static boolean canDrawOnSystemLayer(@NonNull Context context, int systemWindowType) {
if (isSystemLayer(systemWindowType)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
return Settings.canDrawOverlays(context);
} else if (systemWindowType == TYPE_TOAST) {
// since 7.1.1, TYPE_TOAST is not usable since it auto-disappears
// otherwise, just use it since it does not require any special permission
return true;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return Settings.canDrawOverlays(context);
} else {
return hasSystemAlertPermission(context);
}
}
return true;
}
public static boolean hasSystemAlertPermission(@NonNull Context context) {
return PermissionChecker.checkSelfPermission(context, Manifest.permission.SYSTEM_ALERT_WINDOW)
== PermissionChecker.PERMISSION_GRANTED;
}
public static boolean hasSystemAlertPermissionInManifest(@NonNull Context context) {
PackageInfo info = null;
try {
info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Package not found - " + context.getPackageName());
}
if (info != null && info.requestedPermissions != null) {
for (String permission : info.requestedPermissions) {
if (Manifest.permission.SYSTEM_ALERT_WINDOW.equals(permission)) {
return true;
}
}
}
return false;
}
public static boolean isSystemLayer(int windowType) {
return windowType >= FIRST_SYSTEM_WINDOW;
}
public static int getWindowTypeForOverlay(boolean allowSystemLayer) {
if (allowSystemLayer) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
return TYPE_SYSTEM_ALERT;
} else {
return TYPE_TOAST;
}
} else {
// make layout of the window happens as that of a top-level window, not as a child of its container
return TYPE_APPLICATION_ATTACHED_DIALOG;
}
}
}